﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Pfz.Caching;
using Pfz.Threading;

namespace Pfz.Collections
{
	/// <summary>
	/// Dictionary that uses a ReaderWriterLock for its accesses (so the call to each method
	/// is thread-safe) and also gets "trimmed" automatically when garbage collections occur.
	/// 
	/// It is also capable of auto-collecting Values that are empty collections and does not 
	/// accept null values (or remove an item set to null).
	/// </summary>
	public class AutoTrimDictionary<TKey, TValue>:
		ReaderWriterSafeDisposable,
		IDictionary<TKey, TValue>,
		IGarbageCollectionAware
	{
		private Dictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>();

		/// <summary>
		/// Creates a new instance of AutoTrimDictionary.
		/// </summary>
		public AutoTrimDictionary()
		{
			GCUtils.RegisterForCollectedNotification(this);
		}

		/// <summary>
		/// Unregisters this dictionary from GCUtils.Collected.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				GCUtils.UnregisterFromCollectedNotification(this);
				_dictionary = null;
			}

			base.Dispose(disposing);
		}
		void IGarbageCollectionAware.OnCollected()
		{
			try
			{
				using(var upgradeableLock = DisposeLock.UpgradeableLock())
				{
					if (WasDisposed)
					{
						GCUtils.UnregisterFromCollectedNotification(this);
						return;
					}

					Dictionary<TKey, TValue> newDictionary;
					if (MustRemoveEmptyCollections)
					{
						var oldDictionary = _dictionary;
						newDictionary = new Dictionary<TKey, TValue>(_dictionary.Count);

						foreach(var pair in oldDictionary)
						{
							var key = pair.Key;
							var value = pair.Value;

							IEnumerable<object> enumerable = value as IEnumerable<object>;
							if (enumerable != null)
								using (var enumerator = enumerable.GetEnumerator())
									if (!enumerator.MoveNext())
										continue;

							newDictionary.Add(key, value);
						}
					}
					else
						newDictionary = new Dictionary<TKey, TValue>(_dictionary);

					upgradeableLock.Upgrade();
					_dictionary = newDictionary;
				}
			}
			catch
			{
			}
		}

		/// <summary>
		/// Gets or sets a value indicating that values that are empty-collections must be removed
		/// during a collection.
		/// </summary>
		public bool MustRemoveEmptyCollections { get; set; }

		/// <summary>
		/// Redirects the call to the real dictionary, in a thread-safe manner.
		/// </summary>
		public void Add(TKey key, TValue value)
		{
			if (value == null)
				throw new ArgumentNullException("value");

			using(DisposeLock.WriteLock())
			{
				CheckUndisposed();

				_dictionary.Add(key, value);
			}
		}

		/// <summary>
		/// Redirects the call to the real dictionary, in a thread-safe manner.
		/// </summary>
		public bool ContainsKey(TKey key)
		{
			using(DisposeLock.ReadLock())
			{
				CheckUndisposed();

				return _dictionary.ContainsKey(key);
			}
		}

		/// <summary>
		/// Redirects the call to the real dictionary, in a thread-safe manner.
		/// </summary>
		public ICollection<TKey> Keys
		{
			get
			{
				using(DisposeLock.ReadLock())
				{
					CheckUndisposed();

					return _dictionary.Keys.ToList();
				}
			}
		}

		/// <summary>
		/// Redirects the call to the real dictionary, in a thread-safe manner.
		/// </summary>
		public bool Remove(TKey key)
		{
			using(DisposeLock.WriteLock())
			{
				CheckUndisposed();

				return _dictionary.Remove(key);
			}
		}

		/// <summary>
		/// Redirects the call to the real dictionary, in a thread-safe manner.
		/// </summary>
		public bool TryGetValue(TKey key, out TValue value)
		{
			using(DisposeLock.ReadLock())
			{
				CheckUndisposed();

				return _dictionary.TryGetValue(key, out value);
			}
		}

		/// <summary>
		/// Redirects the call to the real dictionary, in a thread-safe manner.
		/// </summary>
		public ICollection<TValue> Values
		{
			get
			{
				using(DisposeLock.ReadLock())
				{
					CheckUndisposed();

					return _dictionary.Values.ToList();
				}
			}
		}

		/// <summary>
		/// Redirects the call to the real dictionary, in a thread-safe manner.
		/// </summary>
		public TValue this[TKey key]
		{
			get
			{
				using(DisposeLock.ReadLock())
				{
					CheckUndisposed();

					return _dictionary[key];
				}
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				using(DisposeLock.WriteLock())
				{
					CheckUndisposed();

					_dictionary[key] = value;
				}
			}
		}


		void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
		{
			if (item.Value == null)
				throw new ArgumentException("item.Value can't be null.");

			using(DisposeLock.WriteLock())
			{
				CheckUndisposed();

				_dictionary.Add(item.Key, item.Value);
			}
		}

		/// <summary>
		/// Redirects the call to the real dictionary, in a thread-safe manner.
		/// </summary>
		public void Clear()
		{
			using(DisposeLock.WriteLock())
			{
				CheckUndisposed();

				_dictionary.Clear();
			}
		}

		bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
		{
			using(DisposeLock.ReadLock())
			{
				CheckUndisposed();
					
				ICollection<KeyValuePair<TKey, TValue>> dictionary = _dictionary;
				return dictionary.Contains(item);
			}
		}

		/// <summary>
		/// Redirects the call to the real dictionary, in a thread-safe manner.
		/// </summary>
		public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
		{
			using(DisposeLock.ReadLock())
			{
				CheckUndisposed();

				ICollection<KeyValuePair<TKey, TValue>> dictionary = _dictionary;
				dictionary.CopyTo(array, arrayIndex);
			}
		}

		/// <summary>
		/// Redirects the call to the real dictionary, in a thread-safe manner.
		/// </summary>
		public int Count
		{
			get
			{
				using(DisposeLock.ReadLock())
				{
					CheckUndisposed();

					return _dictionary.Count;
				}
			}
		}

		bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
		{
			get
			{
				return false;
			}
		}

		bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
		{
			using(DisposeLock.WriteLock())
			{
				CheckUndisposed();

				ICollection<KeyValuePair<TKey, TValue>> dictionary = _dictionary;
				return dictionary.Remove(item);
			}
		}

		/// <summary>
		/// Redirects the call to the real dictionary, in a thread-safe manner.
		/// </summary>
		public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
		{
			using(DisposeLock.ReadLock())
			{
				CheckUndisposed();

				return _dictionary.ToList().GetEnumerator();
			}
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}
	}
}
